package encore.portal.container.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.portlet.ClientDataRequest;
import javax.portlet.PortletRequest;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;

import encore.log.Logger;
import encore.log.LoggerFactory;

public class HttpServletPortletRequestWrapper extends HttpServletRequestWrapper {

	private static final Logger logger=LoggerFactory.getLogger(HttpServletPortletRequestWrapper.class);
	
    protected static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
    protected static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
    protected static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
    protected static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
    protected static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
    protected static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
    protected static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
    protected static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
    protected static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
    protected static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";

    protected static final String[] PATH_ATTRIBUTE_INCLUDE_NAMES = { INCLUDE_CONTEXT_PATH,
                                                                     INCLUDE_PATH_INFO,
                                                                     INCLUDE_QUERY_STRING,
                                                                     INCLUDE_REQUEST_URI,
                                                                     INCLUDE_SERVLET_PATH };

    protected static final String[] PATH_ATTRIBUTE_FORWARD_NAMES = { FORWARD_CONTEXT_PATH,
                                                                     FORWARD_PATH_INFO,
                                                                     FORWARD_QUERY_STRING,
                                                                     FORWARD_REQUEST_URI,
                                                                     FORWARD_SERVLET_PATH };

    protected static final String[] PATH_ATTRIBUTE_NAMES = { INCLUDE_CONTEXT_PATH,
                                                             INCLUDE_PATH_INFO,
                                                             INCLUDE_QUERY_STRING,
                                                             INCLUDE_REQUEST_URI,
                                                             INCLUDE_SERVLET_PATH,
                                                             FORWARD_CONTEXT_PATH,
                                                             FORWARD_PATH_INFO,
                                                             FORWARD_QUERY_STRING,
                                                             FORWARD_REQUEST_URI,
                                                             FORWARD_SERVLET_PATH };
    
    
    protected static final HashSet<String> PATH_ATTRIBUTE_NAMES_SET=new HashSet<String>(Arrays.asList(PATH_ATTRIBUTE_NAMES));
	
	protected static final class PathMethodValues{
	 public String contextPath;
	 public String servletPath;
	 public String pathInfo;
	 public String queryString;
	 public String requestURI;
	 
	 PathMethodValues(){}
	 
	 public PathMethodValues copy(PathMethodValues pathMethodValues)
	 {
		 this.contextPath=pathMethodValues.contextPath;
		 this.servletPath=pathMethodValues.servletPath;
		 this.pathInfo=pathMethodValues.pathInfo;
		 this.queryString=pathMethodValues.queryString;
		 this.requestURI=pathMethodValues.requestURI;
		 return this;
	 }
	 
	};
	
	private static enum DispatchDetection{
	CHECK_STATE,CHECK_REQUEST_WRAPPER_STACK,EVALUATE	
	};
	
	static volatile DispatchDetection dispatchDetection=DispatchDetection.CHECK_STATE;
	
	protected static final HashMap<String, Long> dateHeaderparseCache=new HashMap<String, Long>();
	
	
	protected SimpleDateFormat dateHeaderFormats[]={
			  new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
		        new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
		        new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
			
	};
	
	protected Map<String ,Object> dispPathAttributeValues=new HashMap<String, Object>();
	
	protected Map<String, Object> currPathAttributeValues=new HashMap<String, Object>();
	
	protected Map<String, Object> pathAttributeValues=new HashMap<String, Object>();
	
	protected Map<String, Object> pathAttributeCache=new HashMap<String, Object>();
	
	protected PathMethodValues currPathMethodValues=new PathMethodValues();
	
	protected PathMethodValues dispPathMethodValues=new PathMethodValues();
	
	protected PathMethodValues pathMethodValues=new PathMethodValues();
	
	
	protected PathMethodValues initPathMethodValues;
	
	protected boolean attributeIncludeValuesModified;
	
	protected boolean attributeForwardValuesModified;
	
	protected boolean methodValuesModified;
	
	protected boolean namedDispatch;
	
	protected boolean forwarded;
	
	protected boolean dispatched;
	
	protected boolean nested;
	
	protected int requestWrapperStackSize;
	
	protected Map<String,String[]> origParameterMap;
	
	protected Map<String,String[]>currParameterMap;
	
	protected Map<String, String[]> parameterMap;
	
	protected final ServletContext servletContext;
	
	protected final PortletRequest portletRequest;

	protected final ClientDataRequest clientDataRequest;
	
	protected final String lifecyclePhase;
	
	protected final boolean renderPhase;
	
	protected HttpSession session;
	
	@SuppressWarnings("unchecked")
	public HttpServletPortletRequestWrapper(HttpServletRequest request,ServletContext servletContext,HttpSession session,PortletRequest portletRequest,boolean included,boolean namedDispatch)
	{
		super(request);
		this.servletContext=servletContext;
		this.session=session;
		this.portletRequest=portletRequest;
		this.lifecyclePhase=(String)portletRequest.getAttribute(PortletRequest.LIFECYCLE_PHASE);
		this.clientDataRequest=PortletRequest.ACTION_PHASE.equals(lifecyclePhase)||portletRequest.RESOURCE_PHASE.equals(lifecyclePhase)?(ClientDataRequest)portletRequest:null;
        this.renderPhase=PortletRequest.RENDER_PHASE.equals(lifecyclePhase);
        
        this.forwarded=!included;
        this.namedDispatch=namedDispatch;
        this.origParameterMap=new HashMap<String, String[]>(request.getParameterMap());
        this.currParameterMap=origParameterMap;
	}
	
	private long parseDateHeader(String value)
	{
		Long dateValue=null;
		try
		{
			dateValue=dateHeaderparseCache.get(value);
		}catch (Exception e) {
		   e.printStackTrace();
		}
		
		if(dateValue==null)
		{
			for(int i=0;i<dateHeaderFormats.length;i++)
			{
			   try{
				   Date date=dateHeaderFormats[i].parse(value);
				   dateValue=new Long(date.getTime());
			   }catch (ParseException e) {
				e.printStackTrace();
			}
				
			}
			
			if(dateValue!=null)
			{
				
				 synchronized (dateHeaderparseCache)
	                {
	                    if (dateHeaderparseCache.size() > 1000)
	                    {
	                        dateHeaderparseCache.clear();
	                    }
	                    dateHeaderparseCache.put(value, dateValue);
	                }
			}
			else
			{
				throw new IllegalArgumentException(value);
				
			}
		}
	    
		return dateValue.longValue();
	}
	
	
	public boolean isForwardingPossible()
	{
		return !renderPhase;
	}
	
	public boolean isForwarded()
	{
		return forwarded;
	}
	
   public	boolean isNamedDispatch()
	{
		return namedDispatch;
	}
   
   
   public Map<String,Object> getPathAttributeValues()
   {
	   return new HashMap<String, Object>(dispPathAttributeValues);
   }
   
   
  public PathMethodValues getInitPathMethodValues()
   {
	   return initPathMethodValues!=null? new PathMethodValues().copy(initPathMethodValues):null;
   }
  
  public void setNestedForward()
  {
	  dispatched=false;
	  forwarded=true;
	  this.namedDispatch=false;
	  
  }
  
  public void restoreFromNestedForward(boolean forwarded, boolean namedDispatch,PathMethodValues pathMethodValues,Map<String ,Object> pathAttributeValues)
  {
	  this.forwarded=forwarded;
	  this.namedDispatch=namedDispatch;
	  dispPathAttributeValues.clear();
	  dispPathAttributeValues.putAll(pathAttributeValues);
	  updateRequestPathState();
	  
	  
  }

private void updateRequestPathState() {
	
	syncDispatcherPathValues();
	if(checkDispatcherPathValuesChanged())
	{
		if(!dispatched)
		{
		   initFirstDispatchPathValues();	
		}else
		{
			checkNestedDispatch();
			
		}
		
		if(!nested)
		{
			setupFirstDispatchPathValues();
			
		}else
		{
			setupNestedDispatchPathValues();
		}
	}
	
}


private static boolean compareAttributes(Object o1,Object o2)
{
	return (o1==null&&o2==null)||(o1!=null&&o2!=null&&o1.equals(o2));
}

private static String asString(Object o)
{
	return o!=null?o.toString():null;
}


protected void setupNestedDispatchPathValues()
{
      pathMethodValues.copy(currPathMethodValues);
    
     pathAttributeValues.put(INCLUDE_CONTEXT_PATH, currPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
    pathAttributeValues.put(INCLUDE_SERVLET_PATH, currPathAttributeValues.get(INCLUDE_SERVLET_PATH));
    pathAttributeValues.put(INCLUDE_PATH_INFO, currPathAttributeValues.get(INCLUDE_PATH_INFO));
    pathAttributeValues.put(INCLUDE_QUERY_STRING, currPathAttributeValues.get(INCLUDE_QUERY_STRING));
    pathAttributeValues.put(INCLUDE_REQUEST_URI, currPathAttributeValues.get(INCLUDE_REQUEST_URI));
    
       if (attributeForwardValuesModified && currPathAttributeValues.get(FORWARD_CONTEXT_PATH) != null)
    {
        pathAttributeValues.put(FORWARD_CONTEXT_PATH, currPathAttributeValues.get(FORWARD_CONTEXT_PATH));
        pathAttributeValues.put(FORWARD_SERVLET_PATH, currPathAttributeValues.get(FORWARD_SERVLET_PATH));
        pathAttributeValues.put(FORWARD_PATH_INFO, currPathAttributeValues.get(FORWARD_PATH_INFO));
        pathAttributeValues.put(FORWARD_QUERY_STRING, currPathAttributeValues.get(FORWARD_QUERY_STRING));
        pathAttributeValues.put(FORWARD_REQUEST_URI, currPathAttributeValues.get(FORWARD_REQUEST_URI));
    }
}

protected void setupFirstDispatchPathValues()
{

  pathAttributeValues.clear();
  if (namedDispatch)
  {
     
      pathMethodValues.contextPath = dispPathMethodValues.contextPath;
  }
  else
  {
      if (!forwarded || !isForwardingPossible())
      {
          if (initPathMethodValues != null)
          {
              pathMethodValues.copy(initPathMethodValues);
          }
          else
          {
              pathMethodValues.contextPath = asString(dispPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
              pathMethodValues.servletPath = asString(dispPathAttributeValues.get(INCLUDE_SERVLET_PATH));
              pathMethodValues.pathInfo = asString(dispPathAttributeValues.get(INCLUDE_PATH_INFO));
              pathMethodValues.queryString = asString(dispPathAttributeValues.get(INCLUDE_QUERY_STRING));
              pathMethodValues.requestURI = asString(dispPathAttributeValues.get(INCLUDE_REQUEST_URI));
          }
      }
      else 
      {
          pathMethodValues.copy(dispPathMethodValues);
      }
      
      if (initPathMethodValues == null && pathMethodValues.servletPath != null)
      {
          initPathMethodValues = new PathMethodValues().copy(pathMethodValues);
      }
      
      if (!forwarded)
      {
          pathAttributeValues.put(INCLUDE_CONTEXT_PATH, dispPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
          pathAttributeValues.put(INCLUDE_SERVLET_PATH, dispPathAttributeValues.get(INCLUDE_SERVLET_PATH));
          pathAttributeValues.put(INCLUDE_PATH_INFO, dispPathAttributeValues.get(INCLUDE_PATH_INFO));
          pathAttributeValues.put(INCLUDE_QUERY_STRING, dispPathAttributeValues.get(INCLUDE_QUERY_STRING));
          pathAttributeValues.put(INCLUDE_REQUEST_URI, dispPathAttributeValues.get(INCLUDE_REQUEST_URI));
      }
      else if (initPathMethodValues != null)
      {
          pathAttributeValues.put(FORWARD_CONTEXT_PATH, initPathMethodValues.contextPath);
          pathAttributeValues.put(FORWARD_SERVLET_PATH, initPathMethodValues.servletPath);
          pathAttributeValues.put(FORWARD_PATH_INFO, initPathMethodValues.pathInfo);
          pathAttributeValues.put(FORWARD_QUERY_STRING, initPathMethodValues.queryString);
          pathAttributeValues.put(FORWARD_REQUEST_URI, initPathMethodValues.requestURI);
      }
      else if (forwarded && isForwardingPossible())
      {
          pathAttributeValues.put(FORWARD_CONTEXT_PATH, dispPathMethodValues.contextPath);
          pathAttributeValues.put(FORWARD_SERVLET_PATH, dispPathMethodValues.servletPath);
          pathAttributeValues.put(FORWARD_PATH_INFO, dispPathMethodValues.pathInfo);
          pathAttributeValues.put(FORWARD_QUERY_STRING, dispPathMethodValues.queryString);
          pathAttributeValues.put(FORWARD_REQUEST_URI, dispPathMethodValues.requestURI);
      }
      else // forwarded && !isForwardingPossible()
      {
          pathAttributeValues.put(FORWARD_CONTEXT_PATH, dispPathAttributeValues.get(INCLUDE_CONTEXT_PATH));
          pathAttributeValues.put(FORWARD_SERVLET_PATH, dispPathAttributeValues.get(INCLUDE_SERVLET_PATH));
          pathAttributeValues.put(FORWARD_PATH_INFO, dispPathAttributeValues.get(INCLUDE_PATH_INFO));
          pathAttributeValues.put(FORWARD_QUERY_STRING, dispPathAttributeValues.get(INCLUDE_QUERY_STRING));
          pathAttributeValues.put(FORWARD_REQUEST_URI, dispPathAttributeValues.get(INCLUDE_REQUEST_URI));
      }
  }
}

protected void checkNestedDispatch() {
	 nested = false;
     for (String name : PATH_ATTRIBUTE_NAMES)
     {
         if (!compareAttributes(dispPathAttributeValues.get(name), currPathAttributeValues.get(name)))
         {
             nested = true;
             break;
         }
     }
     nested = nested ? true : !compareAttributes(dispPathMethodValues.contextPath, currPathMethodValues.contextPath);
     nested = nested ? true : !compareAttributes(dispPathMethodValues.servletPath, currPathMethodValues.servletPath);
     nested = nested ? true : !compareAttributes(dispPathMethodValues.pathInfo, currPathMethodValues.pathInfo);
     nested = nested ? true : !compareAttributes(dispPathMethodValues.queryString, currPathMethodValues.queryString);
     nested = nested ? true : !compareAttributes(dispPathMethodValues.requestURI, currPathMethodValues.requestURI);
}

private void initFirstDispatchPathValues() {
	dispatched = true;
	
    dispPathMethodValues.copy(currPathMethodValues);
    dispPathAttributeValues.putAll(currPathAttributeValues);
	
}

protected boolean checkDispatcherPathValuesChanged() {
	// TODO Auto-generated method stub
	   if (methodValuesModified && currPathMethodValues.servletPath == null)
       {
           methodValuesModified = false;
       }
       if (attributeIncludeValuesModified && currPathAttributeValues.get(INCLUDE_SERVLET_PATH) == null)
       {
           attributeIncludeValuesModified = false;
       }
       if (attributeForwardValuesModified && currPathAttributeValues.get(FORWARD_SERVLET_PATH) == null)
       {
           attributeForwardValuesModified = false;
       }
       return (attributeIncludeValuesModified || attributeForwardValuesModified || methodValuesModified);
}

protected void syncDispatcherPathValues() {
	HttpServletRequest request=(HttpServletRequest) getRequest();
	Object attrValue;
	String methodValue;
	
	attributeIncludeValuesModified=false;
	attributeForwardValuesModified=false;
	this.methodValuesModified=false;
	
	for(String name:PATH_ATTRIBUTE_INCLUDE_NAMES)
	{
		attrValue=pathAttributeCache.get(name);
		if(attrValue==null)
		{
			attrValue=request.getAttribute(name);
		}
		
		attributeIncludeValuesModified=!attributeForwardValuesModified?!compareAttributes(currPathAttributeValues.get(name), attrValue):true;
		currPathAttributeValues.put(name,attrValue);
	}
	
	for(String name:PATH_ATTRIBUTE_FORWARD_NAMES)
	{
		
		attrValue=pathAttributeCache.get(name);
		if(attrValue==null)
		{
			attrValue=request.getAttribute(name);
		}
		  attributeForwardValuesModified = !attributeForwardValuesModified ? !compareAttributes(currPathAttributeValues.get(name), attrValue) : true;
       
          currPathAttributeValues.put(name, attrValue);
	}
	
	methodValue=request.getContextPath();
	
	methodValuesModified=methodValuesModified?true:!compareAttributes(currPathMethodValues.contextPath, methodValue);
	currPathMethodValues.contextPath=methodValue;
	
	methodValue=request.getServletPath();
	methodValuesModified=methodValuesModified?true:!compareAttributes(currPathMethodValues.servletPath, methodValue);
	currPathMethodValues.servletPath=methodValue;
	
	methodValue=request.getPathInfo();
	methodValuesModified=methodValuesModified?true:!compareAttributes(currPathMethodValues.pathInfo,methodValue);
	currPathMethodValues.pathInfo=methodValue;
	
    methodValue = request.getQueryString();
    methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.queryString, methodValue);
    currPathMethodValues.queryString = methodValue;

    methodValue = request.getRequestURI();
    methodValuesModified = methodValuesModified ? true : !compareAttributes(currPathMethodValues.requestURI, methodValue);
    currPathMethodValues.requestURI = methodValue;
	
}
	
	
protected boolean isRequestWrapperStackChanged()
{
    HttpServletRequestWrapper currentRequest = this;
    int currentRequestWrapperStackSize = 0;
    while ((currentRequest.getRequest()) instanceof HttpServletRequestWrapper)
    {
        currentRequestWrapperStackSize++;
        currentRequest = (HttpServletRequestWrapper)currentRequest.getRequest();
    }
    if (currentRequestWrapperStackSize != requestWrapperStackSize)
    {
        requestWrapperStackSize = currentRequestWrapperStackSize;
        return true;
    }
    return false;
}


public Map<String, String[]> getParameterMap()
{
    boolean dispatchDetected = false;
    Map<String, String[]> newParameterMap = null;
    
    if (dispatchDetection == DispatchDetection.CHECK_REQUEST_WRAPPER_STACK)
    {
        if (isRequestWrapperStackChanged())
        {
            dispatchDetected = true;
        }
    }
    else
    {
        if (dispatchDetection == DispatchDetection.EVALUATE)
        {
            if (isRequestWrapperStackChanged())
            {
                dispatchDetected = true;
                
                if (logger.isDebugEnabled())
                {
                    logger.debug("DispatchDetection: changing from EVALUATE to CHECK_WEBCONTAINER_REQUEST");
                }
                dispatchDetection = DispatchDetection.CHECK_REQUEST_WRAPPER_STACK;
            }
        }
        if (!dispatchDetected)
        {
            // Use parameters maps comparision to determine if a (nested) dispatch occurred or a return thereof.
            // Note: if a nested dispatch didn't use additional query string parameters,
            // no change will (need to) be detected.
            newParameterMap = getRequest().getParameterMap();
            if (newParameterMap.size() != currParameterMap.size())
            {
                dispatchDetected = true;
            }
            else
            {
                for (Map.Entry<String,String[]> entry : newParameterMap.entrySet())
                {
                    String[] newValues = entry.getValue();
                    String[] currValues = currParameterMap.get(entry.getKey());
                    if (currValues == null || newValues.length != currValues.length)
                    {
                        // no need to compare the actual parameter values as per the servlet spec additional
                        // query string parameters always must be prepended so doing a length check is enough.
                        dispatchDetected = true;
                        break;
                    }
                }
            }
            if (dispatchDetected && dispatchDetection == DispatchDetection.EVALUATE)
            {
                if (logger.isDebugEnabled())
                {
                    logger.debug("DispatchDetection: changing from EVALUATE to CHECK_STATE");
                }
                dispatchDetection = DispatchDetection.CHECK_STATE;
            }
        }
    }

    if (dispatchDetected || parameterMap == null)
    {
        if (newParameterMap == null)
        {
            newParameterMap = getRequest().getParameterMap();
        }
        
        if (dispatchDetection != DispatchDetection.CHECK_REQUEST_WRAPPER_STACK)
        {
            // Save the current parameters map for future comparision
            // Note: this *must* be a copy as some web containers like WebSphere use
            // a "dynamic" parameters map where the content of the current
            // parameters map itself is modified...
            currParameterMap = new HashMap<String,String[]>(newParameterMap);
        }
        Map<String, String[]> diffParameterMap = new HashMap<String, String[]>();
        
        // determine the "diff" between the original parameters map and the current one
        for (Map.Entry<String,String[]> entry : newParameterMap.entrySet())
        {
            String[] values = entry.getValue();
            String[] original = origParameterMap.get(entry.getKey());
            String[] diff = null;
            if ( original == null )
            {
                // a new parameter
                diff = values.clone();
            }
            else if ( values.length > original.length )
            {
                // we've got some additional query string parameter value(s)
                diff = new String[values.length - original.length];
                System.arraycopy(values,0,diff,0,values.length-original.length);
            }
            if ( diff != null )
            {
                diffParameterMap.put(entry.getKey(), diff);
            }
        }
        // we might actually see an empty diff when using DispatchDetection.CHECK_REQUEST_WRAPPER_STACK
        // in which case the work above turned out to be not needed after all and we can retain the
        // current cached parametersMap...
        if (!diffParameterMap.isEmpty())
        {
            // build a new parametersMap by merging the diffParametersMap with the portletRequest.parametersMap
            newParameterMap = new HashMap<String,String[]>(portletRequest.getParameterMap());
            for (Map.Entry<String, String[]> entry : diffParameterMap.entrySet())
            {
                String[] diff = entry.getValue();
                String[] curr = newParameterMap.get(entry.getKey());
                if ( curr == null )
                {
                    newParameterMap.put(entry.getKey(), diff);
                }
                else
                {
                    // we've got some additional query string parameter value(s)
                    String[] copy = new String[curr.length+diff.length];
                    System.arraycopy(diff,0,copy,0,diff.length);
                    System.arraycopy(curr,0,copy,diff.length,curr.length);
                    newParameterMap.put(entry.getKey(), copy);
                }
            }
            parameterMap = Collections.unmodifiableMap(newParameterMap);
        }
    }        
    if (parameterMap == null)
    {
        // first time and no web container provided parameters
        parameterMap = portletRequest.getParameterMap();
    }
    return parameterMap;
}

@Override
public String getParameter(String name)
{
    String[] values = this.getParameterMap().get(name);
    return values != null ? values[0] : null;
}


public Enumeration<String> getParameterNames()
{
  
    return Collections.enumeration(this.getParameterMap().keySet());
}


@Override
public String[] getParameterValues(String name)
{
    // derive from getParametersMap() to ensure the cached parameters map is rebuild
    // when needed for the current (nested) request dispatch
    return this.getParameterMap().get(name);
}

@Override
public String getContextPath()
{
    // synchronize the derived path state values first
    updateRequestPathState();
    // return the derived path method value
    return pathMethodValues.contextPath;
}

@Override
public String getPathInfo()
{
    // synchronize the derived path state values first
    updateRequestPathState();
    // return the derived path method value
    return pathMethodValues.pathInfo;
}

@Override
public String getPathTranslated()
{
    // synchronize the derived path state values first
    updateRequestPathState();
    // base the return value on the derived path method value
    if (pathMethodValues.pathInfo != null && pathMethodValues.contextPath.equals(portletRequest.getContextPath()))
    {
        // can only (and possibly) do this while still within the same context
        return servletContext.getRealPath(pathMethodValues.pathInfo);
    }
    return null;
}

@Override
public String getQueryString()
{
    // synchronize the derived path state values first
    updateRequestPathState();
    // return the derived path method value
    return pathMethodValues.queryString;
}

@Override
public String getRequestURI()
{
    // synchronize the derived path state values first
    updateRequestPathState();
    // return the derived path method value
    return pathMethodValues.requestURI;
}

@Override
public String getServletPath()
{
    // synchronize the derived path state values first
    updateRequestPathState();
    // return the derived path method value
    return pathMethodValues.servletPath;
}

@Override
public Object getAttribute(String name)
{
    if (PATH_ATTRIBUTE_NAMES_SET.contains(name))
    {
        // synchronize the derived path state values first
        updateRequestPathState();
        // return the derived path attribute value
        return pathAttributeValues.get(name);
    }
    // always first try to retrieve the attribute from the web container itself
    Object value = getRequest().getAttribute(name);
    return value != null ? value : portletRequest.getAttribute(name);
}

@Override
public void setAttribute(String name, Object o)
{
    if (PATH_ATTRIBUTE_NAMES_SET.contains(name))
    {
        // path attributes are never set/removed directly to/from the
        // web container but maintained in a separate cache map to 
        // protect against concurrent writing to the client request attribute map(s)
        // when using multi-threaded rendering.
        pathAttributeCache.put(name, o);
    }
    else
    {
        portletRequest.setAttribute(name, o);
    }
}

@Override
public void removeAttribute(String name)
{
    if (PATH_ATTRIBUTE_NAMES_SET.contains(name))
    {
        // path attributes are never set/removed directly to/from the
        // web container but maintained in a separate cache map to 
        // protect against concurrent writing to the client request attribute map(s)
        // when using multi-threaded rendering.
        pathAttributeCache.remove(name);
    }
    else
    {
        portletRequest.removeAttribute(name);
    }
}

@SuppressWarnings("unchecked")
@Override
public Enumeration<String> getAttributeNames()
{
    HashSet<String> names = new HashSet<String>();
    Enumeration<String> e;
    for (e = getRequest().getAttributeNames(); e.hasMoreElements();  )
    {
        try
        {
            names.add(e.nextElement());
        }
        catch(NoSuchElementException nse)
        {
            // ignore potential concurrent changes when run in parallel mode
        }
    }
    for (e = portletRequest.getAttributeNames(); e.hasMoreElements();  )
    {
        try
        {
            names.add(e.nextElement());
        }
        catch(NoSuchElementException nse)
        {
            // ignore potential concurrent changes when run in parallel mode
        }
    }
    // now synchronize the derived path state values before overriding with (or possibly removing from)
    // the actual names set to enumerate
    updateRequestPathState();
    for (String name : PATH_ATTRIBUTE_NAMES)
    {
        if (pathAttributeValues.get(name) != null)
        {
            // ensure the derived path attribute name is present in the set
            names.add(name);
        }
        else
        {
            // remove a possibly web container provided path attribute name
            // if it currently should not be present based on our derived path state
            names.remove(name);
        }
    }
    return Collections.enumeration(names);
}




@Override
public RequestDispatcher getRequestDispatcher(String path)
{
    if (path != null)
    {
        // first determine if the web container does know how to dispatch to this path
        RequestDispatcher dispatcher = super.getRequestDispatcher(path);
        if (dispatcher != null)
        {
            // we have a RequestDispatcher
            if (!dispatched)
            {
                // unlikely, but for sanity sake making sure our internal initial state is created
                updateRequestPathState();
            }
            
            if (forwarded && isForwardingPossible())
            {
                // The webcontainer already will have set the initial request forward path attributes
                // and a subsequent forward (or include) won't need any further special handling
                // therefore we can simply let the webcontainer handle this itself
                return dispatcher;
            }
            else
            {
                // !forwarded || !isForwardingPossible() can and needs to be handled by PortletRequestDispatcherImpl
                // when the dispatch is going to be done using forward because that will require special overriding
                // handling because of portlet spec requirements.
                return new PortletRequestDispatcherImpl(dispatcher, false);
            }
        }
    }
    return null;
}

@Override
public long getDateHeader(String name)
{
    String value = portletRequest.getProperty(name);
    if (value == null)
    { 
        return (-1L);
    }
    // Attempt to convert the date header in a variety of formats
    return parseDateHeader(value);
}

@Override
public String getAuthType()
{
    return portletRequest.getAuthType();
}

@Override
public Cookie[] getCookies()
{
    return portletRequest.getCookies();
}

@Override
public String getHeader(String name)
{
    return portletRequest.getProperty(name);
}

@Override
public Enumeration<String> getHeaderNames()
{
    return portletRequest.getPropertyNames();
}

@Override
public Enumeration<String> getHeaders(String name)
{
    return portletRequest.getProperties(name);
}

@Override
public int getIntHeader(String name)
{
    String property = portletRequest.getProperty(name);
    if (property == null)
    {
        return -1;
    }
    return Integer.parseInt(property);
}

@Override
public String getMethod()
{
    return renderPhase ? "GET" : super.getMethod();
}

@Override
public HttpSession getSession()
{
    return session !=  null ? session : super.getSession();
}

@Override
public HttpSession getSession(boolean create)
{
    return session != null ? session : super.getSession(create);
}

@Override
public String getRemoteUser()
{
    return portletRequest.getRemoteUser();
}

@Override
public String getRequestedSessionId()
{
    return portletRequest.getRequestedSessionId();
}

@Override
public StringBuffer getRequestURL()
{
    return null;
}

@Override
public Principal getUserPrincipal()
{
    return portletRequest.getUserPrincipal();
}

@Override
public boolean isRequestedSessionIdValid()
{
    return portletRequest.isRequestedSessionIdValid();
}

@Override
public boolean isUserInRole(String role)
{
    return portletRequest.isUserInRole(role);
}

@Override
public String getCharacterEncoding()
{
    return clientDataRequest != null ? clientDataRequest.getCharacterEncoding() : null;
}

@Override
public void setCharacterEncoding(String enc) throws UnsupportedEncodingException
{
    if (clientDataRequest != null)
    {
        clientDataRequest.setCharacterEncoding(enc);
    }
}    

@Override
public int getContentLength()
{
    return clientDataRequest != null ? clientDataRequest.getContentLength() : 0;
}

@Override
public String getContentType()
{
    return clientDataRequest != null ? clientDataRequest.getContentType() : null;
}

@Override
public ServletInputStream getInputStream() throws IOException
{
    return clientDataRequest != null ? (ServletInputStream)clientDataRequest.getPortletInputStream() : null;
}

@Override
public String getLocalAddr()
{
    return null;
}

@Override
public Locale getLocale()
{
    return portletRequest.getLocale();
}

@Override
public Enumeration<Locale> getLocales()
{
    return portletRequest.getLocales();
}

@Override
public String getLocalName()
{
    return null;
}

@Override
public int getLocalPort()
{
    return 0;
}

@Override
public String getProtocol()
{
    return "HTTP/1.1";
}

@Override
public BufferedReader getReader() throws IOException
{
    return clientDataRequest != null ? clientDataRequest.getReader() : null;
}

@Override
public String getRealPath(String path)
{
    return null;
}

@Override
public String getRemoteAddr()
{
    return null;
}

@Override
public String getRemoteHost()
{
    return null;
}

@Override
public int getRemotePort()
{
    return 0;
}

@Override
public String getScheme()
{
    return portletRequest.getScheme();
}

@Override
public String getServerName()
{
    return portletRequest.getServerName();
}

@Override
public int getServerPort()
{
    return portletRequest.getServerPort();
}

@Override
public boolean isSecure()
{
    return portletRequest.isSecure();
}


	
	
    

}
